function getSinMul100(deg){
	var sin=[0,1,3,5,6,8,10,12,13,15,17,19,20,22,24,25,27,29,30,32,34,35,37,39,40,42,43,45,46,48,49,51,52,54,55,57,58,60,61,62,64,65,66,68,69,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,89,90,91,92,92,93,93,94,95,95,96,96,97,97,97,98,98,98,99,99,99,99,99,99,99,99,100];
	deg=(deg%360+360)%360; //deg in [0,359]
	if(deg<=90){
		return sin[deg];
	};
	if(deg<=180){
		return sin[180-deg];
	};
	if(deg<=270){
		return -sin[deg-180];
	};
	return -sin[360-deg];
};
function getCosMul100(deg){
	deg+=90;
	return getSinMul100(deg);
};

//CLASS_________________________________

(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};
  
  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
    
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
    
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" && 
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
            
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
            
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;
            
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
    
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
    
    // Populate our constructed prototype object
    Class.prototype = prototype;
    
    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;
    
    return Class;
  };
})();

Function.prototype.inScope = function(scope)
{
	var func = this;
	return function()
	{
		return func.apply(scope, arguments);
	}
}

//BALL__________________________________
var Ball=Class.extend({
	x:null,
	y:null,
	r:15,
	speed:0,
	deg:0,
	init:function(x,y){
		this.x=x||0;
		this.y=y||0;
	}
});

//PALKA___________________________________
var Palka = Class.extend({
	x: null,
	y: null,
	len: null,
	speed: 0,
	init: function(x, y, len){
		this.x=x||0;
		this.y=y||0;
		this.len=len||0;
	},
	upDate: function(min, max){
		this.y+= this.speed;
		if(this.y<min){
			this.y=min;
		};
		if(this.y>max){
			this.y=max
		};
	}
});

//LINES______________________________________
function linesIntersection(x1,y1,x2,y2,x3,y3,x4,y4){
	if(!(x1!=null&&x2!=null&&y1!=null&&
		y2!=null&&x3!=null&&x4!=null&&
		y3!=null&&y4!=null)){
		return null;
	}
	var answer=[2];
	var a1=y1-y2;
	var b1=x2-x1;
	var c1=x1*y2-x2*y1;
	var a2=y3-y4;
	var b2=x4-x3;
	var c2=x3*y4-x4*y3;
	var s=a1*b2-a2*b1
	if(!(s)){
		return null;
	}
	answer[0]=(c2*b1-c1*b2)/s;
	answer[1]=(c1*a2-c2*a1)/s;
	return answer;
}

function segmentsIntersection(x1,y1,x2,y2,x3,y3,x4,y4){
	var answer=linesIntersection(x1,y1,x2,y2,x3,y3,x4,y4);
	if(answer==null){
		return null;
	}
	var s;
	if(x1>x2){
		s=x1;
		x1=x2;
		x2=s;
	}
	if(x3>x4){
		s=x3;
		x3=x4;
		x4=s;
	}
	if(y1>y2){
		s=y1;
		y1=y2;
		y2=s;
	}
	if(y3>y4){
		s=y3;
		y3=y4;
		y4=s;
	}
	var dx=0.01;
	x1-=dx;
	x2+=dx;
	y1-=dx;
	y2+=dx;
	x3-=dx;
	x4+=dx;
	y3-=dx;
	y4+=dx;
	if((answer[0]<=x2&&answer[0]<=x4&&answer[0]>=x1&&answer[0]>=x3)&&
		(answer[1]<=y2&&answer[1]<=y4&&answer[1]>=y1&&answer[1]>=y3)){
		return answer;
	}else{
		return null;
	}
}

function rotate(ball,deg){

	var speedX=ball.speedX;

	ball.speedX=ball.speedX*Math.cos(deg)-ball.speedY*Math.sin(deg);

	ball.speedY=speedX*Math.sin(deg)+ball.speedY*Math.cos(deg);

	var x=ball.speedX;

	var y=ball.speedY;

	var len=Math.sqrt(x*x+y*y);

	x/=len;

	y/=len;

	if(x<0.5&&x>-0.5){
		rotate(ball,-deg);
	}

}
//SERVERGAMECONTROLLER___________________________
var serverGameController = Class.extend({
	id: null,
	readyCheck: 0,
	score:null,
	winScore: 10,
	palka1: null,
	palka2: null,
	player1: null,
	player2: null,
	timer : null,
	ball : null,
	width: 640,
	height: 400,
	startSpeed: 20,
	len1: 200,
	len2: 200,
	tick: 0,
	waitTime: 3, //тики ожидания
	moveControll: null,
	startTime:0,
	dTime:0,
	stop: function(){
		clearInterval(this.timer);
		this.player1.json.send({'event': 'stop'});
		this.player2.json.send({'event': 'stop'});
	},
	init : function(player1, player2, id){
		this.score=[0,0];
		this.id=id;
		this.moveControll=[-1,-1,-1,-1,-1,-1]
		this.player1=player1;
		this.player2=player2;
		this.ball=new Ball(this.width/2, this.height/2-20);
		this.palka1=new Palka(10, this.height/2-this.len1/2, this.len1);
		this.palka2=new Palka(this.width-10, this.height/2-this.len2/2, this.len2);
		this.player1.on('message', this.onMsg1.inScope(this));
		this.player2.on('message', this.onMsg2.inScope(this));
	},
	moveControllProcess : function(){
		if(this.tick==this.moveControll[0]){
			this.palka1.speed=-15;
		};
		if(this.tick==this.moveControll[3]){
			this.palka2.speed=-15;
		};
		if(this.tick==this.moveControll[1]){
			this.palka1.speed=15;
		};
		if(this.tick==this.moveControll[4]){
			this.palka2.speed=15;
		};
		if(this.tick==this.moveControll[2]){
			this.palka1.speed=0;
		};
		if(this.tick==this.moveControll[5]){
			this.palka2.speed=0;
		};
	},
	onMsg1:function(msg){
		if(msg.event=='disconnect'){
			this.stop();
			this.player2.json.send({'event': 'opponentDisconnect'});
			return;
		};
		if(msg.event=='ready'){
			this.player1.json.send({'event': 'ready', 'player': true});
			this.player2.json.send({'event': 'ready', 'player': false});
			if(this.readyCheck==2){
				this.readyCheck=3;
				this.start();
			}else{
				this.readyCheck=1;
			};
			return;
		};
		if(msg.event=='moveUp'){
			if(this.moveControll[0]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'moveUp1', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'moveUp2', 'tick': this.tick+
				this.waitTime});
			this.moveControll[0]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='moveDown'){
			if(this.moveControll[1]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'moveDown1', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'moveDown2', 'tick': this.tick+
				this.waitTime});
			this.moveControll[1]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='stopMove'){
			if(this.moveControll[2]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'stopMove1', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'stopMove2', 'tick': this.tick+
				this.waitTime});
			this.moveControll[2]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='synchronizationError'){
			if(this.waitTime<5){
				this.waitTime+=1;
			};
			var data=[this.tick,this.ball,
						this.palka2,this.palka1,
						this.moveControll, this.score];
			this.player1.json.send({'event': 'update', 'data': data});
			return;
		};
	},
	onMsg2:function(msg){
		if(msg.event=='disconnect'){
			this.player1.json.send({'event': 'opponentDisconnect'});
			this.stop();
			return;
		};
		if(msg.event=='ready'){
			this.player1.json.send({'event': 'ready', 'player': false});
			this.player2.json.send({'event': 'ready', 'player': true});
			if(this.readyCheck==1){
				this.readyCheck=3;
				this.start();
			}else{
				this.readyCheck=2;
			};
			return;
		};
		if(msg.event=='moveUp'){
			if(this.moveControll[3]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'moveUp2', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'moveUp1', 'tick': this.tick+
				this.waitTime});
			this.moveControll[3]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='moveDown'){
			if(this.moveControll[4]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'moveDown2', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'moveDown1', 'tick': this.tick+
				this.waitTime});
			this.moveControll[4]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='stopMove'){
			if(this.moveControll[5]>this.tick){
				return;
			};
			this.player1.json.send({'event': 'stopMove2', 'tick': this.tick+
				this.waitTime});
			this.player2.json.send({'event': 'stopMove1', 'tick': this.tick+
				this.waitTime});
			this.moveControll[5]=this.tick+this.waitTime;
			return;
		};
		if(msg.event=='synchronizationError'){
			if(this.waitTime<5){
				this.waitTime+=1;
			};
			var data=[this.tick,this.ball,
						this.palka1,this.palka2,
						this.moveControll, this.score];
			this.player2.json.send({'event': 'update', 'data': data});
			return;
		};
	},
	start:function(){
		var rand=(new Date).getMilliseconds()%2;
		if(rand==0){
			rand=0;
		}else{
			rand=180;
		};
		this.ball.speed=this.startSpeed;
		this.ball.deg=rand;
		this.player1.json.send({'event': 'start', 'startSpeed': this.startSpeed,
			'deg': rand});
		this.player2.json.send({'event': 'start', 'startSpeed': this.startSpeed,
			'deg': rand+180});
		this.startTime=new Date;
		this.timer=setInterval(this.onTimer.inScope(this),40);
	},
	
	onTimer : function(){
		this.tick+=1;
		this.dTime+=(new Date-this.oldTime)-40;
		this.oldTime=new Date;
		this.moveControllProcess();
		this.palka1.upDate(0,this.height-this.palka1.len);
		this.palka2.upDate(0,this.height-this.palka2.len);
		var x=this.ball.x;
		var y=this.ball.y;
		this.ball.x+=(this.ball.speed*getCosMul100(this.ball.deg)-
			this.ball.speed*getCosMul100(this.ball.deg)%100)/100;
		this.ball.y+=(this.ball.speed*getSinMul100(this.ball.deg)-
			this.ball.speed*getSinMul100(this.ball.deg)%100)/100;
		var point=null;
		if(this.ball.y<0){
			this.ball.deg=-this.ball.deg;
			this.ball.y=-this.ball.y;
		}
		if(this.ball.y>this.height){
			this.ball.deg=-this.ball.deg;
			this.ball.y=2*this.height-this.ball.y;
		}
		if(x>this.ball.x){
			point=segmentsIntersection(x,y,this.ball.x,this.ball.y,
				this.palka1.x+(this.ball.r+10),this.palka1.y,this.palka1.x+
				(this.ball.r+10),this.palka1.y+this.palka1.len);
		
			if(point){
				this.ball.deg=180-this.ball.deg;
				this.ball.x=2*(this.palka1.x+(this.ball.r+10))-this.ball.x;
				var k=point[1]-this.palka1.y;
				var deg=-(((2*k-this.palka1.len)/this.palka1.len)*30);
				deg-=deg%1;
				this.ball.deg-=deg;
				this.ball.deg=(this.ball.deg%360+360)%360;
				if(this.ball.deg>60&&this.ball.deg<=90){
					this.ball.deg=60;
				}else{
					if(this.ball.deg<300&&this.ball.deg>=270){
						this.ball.deg=300;
					};
				};
			}
		}
		if(x<this.ball.x){
			point=segmentsIntersection(x,y,this.ball.x,this.ball.y,
				this.palka2.x-(this.ball.r+10),this.palka2.y,this.palka2.x-
				(this.ball.r+10),this.palka2.y+this.palka1.len);
		
			if(point){
				this.ball.deg=180-this.ball.deg;
				this.ball.x=2*(this.palka2.x-(this.ball.r+10))-this.ball.x;
				var k=point[1]-this.palka2.y;
				var deg=-(((2*k-this.palka2.len)/this.palka2.len)*30);
				deg-=deg%1;
				this.ball.deg+=deg;
				this.ball.deg-=180;
				this.ball.deg=(this.ball.deg%360+360)%360;
				if(this.ball.deg>60&&this.ball.deg<=90){
					this.ball.deg=60;
				}else{
					if(this.ball.deg<300&&this.ball.deg>=270){
						this.ball.deg=300;
					};
				};
				this.ball.deg-=180;
			}
		}
		if(this.ball.x<0){
			this.ball.deg=180-this.ball.deg;
			this.ball.x=-this.ball.x;
			this.score[1]+=1;
			this.player1.json.send({'event':'scoreUpDate', 'player': false});
			this.player2.json.send({'event':'scoreUpDate', 'player': true});
			if(this.score[1]==this.winScore){
				this.stop();
				this.player2.json.send({'event': 'win', 'player': true});
				this.player1.json.send({'event': 'win', 'player': false});
			};
		}
		if(this.ball.x>this.width){
			this.ball.deg=180-this.ball.deg;
			this.ball.x=this.width*2-this.ball.x;
			this.score[0]+=1;
			this.player1.json.send({'event':'scoreUpDate', 'player': true});
			this.player2.json.send({'event':'scoreUpDate', 'player': false});
			if(this.score[0]==this.winScore){
				this.stop();
				this.player1.json.send({'event': 'win', 'player': true});
				this.player2.json.send({'event': 'win', 'player': false});
			};
		}
		this.dTime=new Date-this.tick*40-this.startTime;
		if(this.dTime>40){
			this.onTimer();
		}else{
			if(this.dTime<-40){
				return;
			};
		};
	}
});


function game(player1, player2, id){
	console.log('Game '+id+' started. '+player1.id+' vs '+player2.id);
	var gameController=new serverGameController(player1, player2, id);
	player1.on('disconnect', function() {
		if(gameController){
			gameController.stop();
			console.log('Game '+id+' finished. '+player1.id+' vs '+player2.id);
			delete gameController;
			gameController=0;
		};
	});
	player2.on('disconnect', function() {
		if(gameController){
			gameController.stop();
			console.log('Game '+id+' finished. '+player1.id+' vs '+player2.id);
			delete gameController;
			gameController=0;
		};
	});
};

var io = require('socket.io').listen(8080); 
io.set('log level', 0);

//массив клиентов: ID клиента, клиент, в игре, ожидает соперника.
var socketsArr=[];
//активные соединения
var connections=0;
var id=0;
io.sockets.on('connection', function (socket) {
	connections+=1;
	var position=socketsArr.length;
	socketsArr.push([socket.id, false, false]);
	var time = (new Date).toLocaleTimeString();
	socket.json.send({'event': 'connected', 'time': time});
	console.log(socket.id+' connected at '+ time);
	socket.on('message', function (msg) {
		if(msg.event=='startGame'){
			if(!socketsArr[position][2]){
				socketsArr[position][2]=true;
				for(i=0;i<socketsArr.length;i+=1){
					if(socketsArr[i]&&socketsArr[i][2]&&(i!=position)){
						socketsArr[i][2]=false;
						socketsArr[position][2]=false;
						io.sockets.sockets[socketsArr[position][0]].json.send({
							'event': 'startGame'
						});
						io.sockets.sockets[socketsArr[i][0]].json.send({
							'event': 'startGame'
						});
						id+=1;
						game(io.sockets.sockets[socketsArr[position][0]],
							io.sockets.sockets[socketsArr[i][0]], id);
						break;
					};
				};
			};
			console.log('Event "'+msg.event+'" from '+ socket.id);
			return;
		};
		if(msg.event=='synchronizationError'){
			console.log('Event "'+msg.event+'" from '+ socket.id+', different is '+msg.different);
			return;
		};
		if(msg.event=='ping'){
			socket.json.send({'event': 'ping', 'time': msg.time, 'connections': connections});
			return;
		};
		console.log('Event "'+msg.event+'" from '+ socket.id);
	});
	socket.on('disconnect', function() {
		console.log(socket.id+' disconnected');
		connections-=1;
		socketsArr[position]=0;
	});
});